package com.tegtech.starter.redis.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
/**
 * RedisGeo操作类
 * @Author Jun
 * @date 2020年4月18日 下午11:14:49
 */
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisGeo {
 
    @Autowired
    RedisTemplate redisTemplate;
 
    /**
	 * 添加经纬度信息,时间复杂度为O(log(N))
     * redis 命令：geoadd cityGeo 116.405285 39.904989 "北京"
	 * @param k		缓存的Key
	 * @param lng	经度
	 * @param lat	纬度
	 * @param m		对应的member
	 * @return
	 */
    public Long addGeoPoint(String k, Double lng, Double lat, Object m) {
        Long addedNum = redisTemplate.opsForGeo().add(k, new Point(lng, lat), m);
        return addedNum;
    }
 
    /**
     * 查找指定key的经纬度信息，可以指定多个member，批量返回
     * redis命令：geopos cityGeo 北京
     * @param k
     * @param m
     */
    public List<Point> geoGet(String k, Object... m) {
        List<Point> points = redisTemplate.opsForGeo().position(k, m);
        return points;
    }
 
    /**
     * 返回两个地方的距离，可以指定单位，比如米m，千米km，英里mi，英尺ft
     * redis命令：geodist cityGeo 北京 上海
     * @param k
     * @param mk1
     * @param mk2
     * @param metric
     * @return
     */
    public Distance geoDist(String k, Object mk1, Object mk2, Metric metric) {
        Distance distance = redisTemplate.opsForGeo().distance(k, mk1, mk2, metric);
        return distance;
    }
    
    /**
     * 返回两个地方的距离，单位:m
     * redis命令：geodist cityGeo 北京 上海
     * @param k
     * @param mk1
     * @param mk2
     * @param metric
     * @return
     */
    public Distance geoDist(String k, Object mk1, Object mk2) {
        return this.geoDist(k, mk1, mk2, RedisGeoCommands.DistanceUnit.METERS);
    }
 
    /**
     * 根据给定的经纬度，返回半径不超过指定距离的元素,时间复杂度为O(N+log(M))，N为指定半径范围内的元素个数，M为要返回的个数
     * redis命令：georadius cityGeo 116.405285 39.904989 100 km WITHDIST WITHCOORD ASC COUNT 5
     * @param k		缓存Key
     * @param lng	经度
	 * @param lat	纬度
	 * @param raidus指定半径，单位：m
     * @param n		返回元素的个数
     * @return
     */
    public GeoResults nearByXY(String k, Double lng, Double lat, double raidus, int n) {
//        Circle circle = new Circle(lng, lat, Metrics.KILOMETERS.getMultiplier());
        Circle circle = new Circle(lng, lat, raidus);
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(n);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo().radius(k, circle, args);
        return results;
    }
 
    /**
     * 根据指定的地点查询半径在指定范围内的位置,时间复杂度为O(log(N)+M)，N为指定半径范围内的元素个数，M为要返回的个数
     * redis命令：georadiusbymember cityGeo 北京 100 km WITHDIST WITHCOORD ASC COUNT 5
     * @param k
     * @param mk
     * @param raidus	指定半径，单位：m
     * @param n			返回元素的个数
     * @return
     */
    public <T> GeoResults nearByPlace(String k, Object mk, double raidus, int n) {
        Distance distance = new Distance(raidus, Metrics.NEUTRAL);
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(n);
        GeoResults<RedisGeoCommands.GeoLocation<T>> results = redisTemplate.opsForGeo().radius(k, mk, distance, args); 
        return results;
    }
 
    /**
     * 返回的是geohash值,查找一个位置的时间复杂度为O(log(N))
     * redis命令：geohash cityGeo 北京
     * @param k
     * @param mks
     * @return
     */
    public List geoHash(String k, Object... mks) {
        List<String> results = redisTemplate.opsForGeo().hash(k, mks);
        return results;
    }
    
    /**
     * 移除指定的Geo信息，可指定多个member
     * @param k
     * @param mks
     * @return
     */
    public Long remove(String k, Object... mks) {
    	Long removeNum = redisTemplate.opsForGeo().remove(k, mks);
    	return removeNum;
    }
    
}
